Lietuvių

Išnagrinėkite dvejetainių paieškos medžių (BST) pagrindus ir sužinokite, kaip juos efektyviai įdiegti naudojant JavaScript. Šis vadovas apima struktūrą, operacijas ir praktinius pavyzdžius.

Dvejetainiai paieškos medžiai: išsamus diegimo vadovas naudojant JavaScript

Dvejetainiai paieškos medžiai (angl. Binary Search Trees, BST) yra pagrindinė kompiuterių mokslo duomenų struktūra, plačiai naudojama efektyviai duomenų paieškai, rūšiavimui ir gavimui. Jų hierarchinė struktūra leidžia daugeliui operacijų pasiekti logaritminį laiko sudėtingumą, todėl jie yra galingas įrankis valdant didelius duomenų rinkinius. Šiame vadove pateikiama išsami BST apžvalga ir demonstruojamas jų diegimas naudojant JavaScript, skirtas programuotojams visame pasaulyje.

Dvejetainių paieškos medžių supratimas

Kas yra dvejetainis paieškos medis?

Dvejetainis paieškos medis yra medžio tipo duomenų struktūra, kurioje kiekviena viršūnė turi ne daugiau kaip du vaikus, vadinamus kairiuoju vaiku ir dešiniuoju vaiku. Pagrindinė BST savybė yra ta, kad bet kuriai duotai viršūnei:

Ši savybė užtikrina, kad elementai BST visada yra surūšiuoti, o tai leidžia efektyviai ieškoti ir gauti duomenis.

Pagrindinės sąvokos

Dvejetainio paieškos medžio diegimas JavaScript

Viršūnės klasės apibrėžimas

Pirmiausia apibrėžiame `Node` klasę, kuri atstovaus kiekvieną BST viršūnę. Kiekviena viršūnė turės `key` duomenims saugoti ir `left` bei `right` rodykles į savo vaikus.


class Node {
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}

Dvejetainio paieškos medžio klasės apibrėžimas

Toliau apibrėžiame `BinarySearchTree` klasę. Šioje klasėje bus šaknies viršūnė ir metodai, skirti medžiui įterpti, ieškoti, šalinti ir apeiti.


class BinarySearchTree {
  constructor() {
    this.root = null;
  }

  // Čia bus pridėti metodai
}

Įterpimas

`insert` metodas prideda naują viršūnę su duotu raktu į BST. Įterpimo procesas išlaiko BST savybę, įdėdamas naują viršūnę į tinkamą poziciją, palyginti su esamomis viršūnėmis.


insert(key) {
  const newNode = new Node(key);

  if (this.root === null) {
    this.root = newNode;
  } else {
    this.insertNode(this.root, newNode);
  }
}

insertNode(node, newNode) {
  if (newNode.key < node.key) {
    if (node.left === null) {
      node.left = newNode;
    } else {
      this.insertNode(node.left, newNode);
    }
  } else {
    if (node.right === null) {
      node.right = newNode;
    } else {
      this.insertNode(node.right, newNode);
    }
  }
}

Pavyzdys: reikšmių įterpimas į BST


const bst = new BinarySearchTree();
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);

Paieška

`search` metodas patikrina, ar BST egzistuoja viršūnė su duotu raktu. Jis apeina medį, lygindamas raktą su dabartinės viršūnės raktu ir atitinkamai pereidamas į kairįjį ar dešinįjį pomedį.


search(key) {
  return this.searchNode(this.root, key);
}

searchNode(node, key) {
  if (node === null) {
    return false;
  }

  if (key < node.key) {
    return this.searchNode(node.left, key);
  } else if (key > node.key) {
    return this.searchNode(node.right, key);
  } else {
    return true;
  }
}

Pavyzdys: reikšmės paieška BST


console.log(bst.search(9));  // Išvestis: true
console.log(bst.search(2));  // Išvestis: false

Šalinimas

`remove` metodas pašalina viršūnę su duotu raktu iš BST. Tai sudėtingiausia operacija, nes šalinant viršūnę reikia išlaikyti BST savybę. Reikia apsvarstyti tris atvejus:


remove(key) {
  this.root = this.removeNode(this.root, key);
}

removeNode(node, key) {
  if (node === null) {
    return null;
  }

  if (key < node.key) {
    node.left = this.removeNode(node.left, key);
    return node;
  } else if (key > node.key) {
    node.right = this.removeNode(node.right, key);
    return node;
  } else {
    // raktas yra lygus viršūnės raktui

    // 1 atvejis - lapo viršūnė
    if (node.left === null && node.right === null) {
      node = null;
      return node;
    }

    // 2 atvejis - viršūnė turi tik 1 vaiką
    if (node.left === null) {
      node = node.right;
      return node;
    } else if (node.right === null) {
      node = node.left;
      return node;
    }

    // 3 atvejis - viršūnė turi 2 vaikus
    const aux = this.findMinNode(node.right);
    node.key = aux.key;
    node.right = this.removeNode(node.right, aux.key);
    return node;
  }
}

findMinNode(node) {
  let current = node;
  while (current != null && current.left != null) {
    current = current.left;
  }
  return current;
}

Pavyzdys: reikšmės šalinimas iš BST


bst.remove(7);
console.log(bst.search(7)); // Išvestis: false

Medžio apėjimas

Medžio apėjimas apima kiekvienos medžio viršūnės aplankymą tam tikra tvarka. Yra keletas įprastų apėjimo metodų:


inOrderTraverse(callback) {
  this.inOrderTraverseNode(this.root, callback);
}

inOrderTraverseNode(node, callback) {
  if (node !== null) {
    this.inOrderTraverseNode(node.left, callback);
    callback(node.key);
    this.inOrderTraverseNode(node.right, callback);
  }
}

preOrderTraverse(callback) {
  this.preOrderTraverseNode(this.root, callback);
}

preOrderTraverseNode(node, callback) {
  if (node !== null) {
    callback(node.key);
    this.preOrderTraverseNode(node.left, callback);
    this.preOrderTraverseNode(node.right, callback);
  }
}

postOrderTraverse(callback) {
  this.postOrderTraverseNode(this.root, callback);
}

postOrderTraverseNode(node, callback) {
  if (node !== null) {
    this.postOrderTraverseNode(node.left, callback);
    this.postOrderTraverseNode(node.right, callback);
    callback(node.key);
  }
}

Pavyzdys: BST apėjimas


const printNode = (value) => console.log(value);

bst.inOrderTraverse(printNode);   // Išvestis: 3 5 8 9 10 11 12 13 14 15 18 20 25
bst.preOrderTraverse(printNode);  // Išvestis: 11 5 3 9 8 10 15 13 12 14 20 18 25
bst.postOrderTraverse(printNode); // Išvestis: 3 8 10 9 12 14 13 18 25 20 15 11

Mažiausios ir didžiausios reikšmės

Mažiausios ir didžiausios reikšmės radimas BST yra paprastas dėl jo tvarkingos prigimties.


min() {
  return this.minNode(this.root);
}

minNode(node) {
  let current = node;
  while (current !== null && current.left !== null) {
    current = current.left;
  }
  return current;
}

max() {
  return this.maxNode(this.root);
}

maxNode(node) {
  let current = node;
  while (current !== null && current.right !== null) {
    current = current.right;
  }
  return current;
}

Pavyzdys: mažiausios ir didžiausios reikšmių radimas


console.log(bst.min().key); // Išvestis: 3
console.log(bst.max().key); // Išvestis: 25

Praktinis dvejetainių paieškos medžių taikymas

Dvejetainiai paieškos medžiai naudojami įvairiose srityse, įskaitant:

Našumo aspektai

BST našumas priklauso nuo jo struktūros. Geriausiu atveju, subalansuotas BST leidžia pasiekti logaritminį laiko sudėtingumą įterpimo, paieškos ir šalinimo operacijoms. Tačiau blogiausiu atveju (pvz., iškrypęs medis), laiko sudėtingumas gali pablogėti iki tiesinio laiko.

Subalansuoti ir nesubalansuoti medžiai

Subalansuotas BST yra toks, kuriame bet kurios viršūnės kairiojo ir dešiniojo pomedžių aukščiai skiriasi ne daugiau kaip vienu. Savaime balansuojantys algoritmai, tokie kaip AVL medžiai ir raudonai-juodi medžiai, užtikrina, kad medis išliktų subalansuotas, suteikdamas pastovų našumą. Skirtingiems regionams gali prireikti skirtingų optimizavimo lygių, atsižvelgiant į serverio apkrovą; balansavimas padeda išlaikyti našumą esant dideliam pasauliniam naudojimui.

Laiko sudėtingumas

Pažangios BST sąvokos

Savaime balansuojantys medžiai

Savaime balansuojantys medžiai yra BST, kurie automatiškai koreguoja savo struktūrą, kad išlaikytų pusiausvyrą. Tai užtikrina, kad medžio aukštis išliktų logaritminis, suteikdamas pastovų našumą visoms operacijoms. Įprasti savaime balansuojantys medžiai apima AVL medžius ir raudonai-juodus medžius.

AVL medžiai

AVL medžiai palaiko pusiausvyrą užtikrindami, kad aukščio skirtumas tarp bet kurios viršūnės kairiojo ir dešiniojo pomedžių būtų ne didesnis kaip vienas. Kai ši pusiausvyra sutrinka, atliekami pasukimai pusiausvyrai atkurti.

Raudonai-juodi medžiai

Raudonai-juodi medžiai naudoja spalvų savybes (raudona arba juoda) pusiausvyrai palaikyti. Jie yra sudėtingesni nei AVL medžiai, tačiau tam tikrais atvejais siūlo geresnį našumą.

JavaScript kodo pavyzdys: Pilnas dvejetainio paieškos medžio diegimas


class Node {
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}

class BinarySearchTree {
  constructor() {
    this.root = null;
  }

  insert(key) {
    const newNode = new Node(key);

    if (this.root === null) {
      this.root = newNode;
    } else {
      this.insertNode(this.root, newNode);
    }
  }

  insertNode(node, newNode) {
    if (newNode.key < node.key) {
      if (node.left === null) {
        node.left = newNode;
      } else {
        this.insertNode(node.left, newNode);
      }
    } else {
      if (node.right === null) {
        node.right = newNode;
      } else {
        this.insertNode(node.right, newNode);
      }
    }
  }

  search(key) {
    return this.searchNode(this.root, key);
  }

  searchNode(node, key) {
    if (node === null) {
      return false;
    }

    if (key < node.key) {
      return this.searchNode(node.left, key);
    } else if (key > node.key) {
      return this.searchNode(node.right, key);
    } else {
      return true;
    }
  }

  remove(key) {
    this.root = this.removeNode(this.root, key);
  }

  removeNode(node, key) {
    if (node === null) {
      return null;
    }

    if (key < node.key) {
      node.left = this.removeNode(node.left, key);
      return node;
    } else if (key > node.key) {
      node.right = this.removeNode(node.right, key);
      return node;
    } else {
      // raktas yra lygus viršūnės raktui

      // 1 atvejis - lapo viršūnė
      if (node.left === null && node.right === null) {
        node = null;
        return node;
      }

      // 2 atvejis - viršūnė turi tik 1 vaiką
      if (node.left === null) {
        node = node.right;
        return node;
      } else if (node.right === null) {
        node = node.left;
        return node;
      }

      // 3 atvejis - viršūnė turi 2 vaikus
      const aux = this.findMinNode(node.right);
      node.key = aux.key;
      node.right = this.removeNode(node.right, aux.key);
      return node;
    }
  }

  findMinNode(node) {
    let current = node;
    while (current != null && current.left != null) {
      current = current.left;
    }
    return current;
  }

  min() {
    return this.minNode(this.root);
  }

  minNode(node) {
    let current = node;
    while (current !== null && current.left !== null) {
      current = current.left;
    }
    return current;
  }

  max() {
    return this.maxNode(this.root);
  }

  maxNode(node) {
    let current = node;
    while (current !== null && current.right !== null) {
      current = current.right;
    }
    return current;
  }

  inOrderTraverse(callback) {
    this.inOrderTraverseNode(this.root, callback);
  }

  inOrderTraverseNode(node, callback) {
    if (node !== null) {
      this.inOrderTraverseNode(node.left, callback);
      callback(node.key);
      this.inOrderTraverseNode(node.right, callback);
    }
  }

  preOrderTraverse(callback) {
    this.preOrderTraverseNode(this.root, callback);
  }

  preOrderTraverseNode(node, callback) {
    if (node !== null) {
      callback(node.key);
      this.preOrderTraverseNode(node.left, callback);
      this.preOrderTraverseNode(node.right, callback);
    }
  }

  postOrderTraverse(callback) {
    this.postOrderTraverseNode(this.root, callback);
  }

  postOrderTraverseNode(node, callback) {
    if (node !== null) {
      this.postOrderTraverseNode(node.left, callback);
      this.postOrderTraverseNode(node.right, callback);
      callback(node.key);
    }
  }
}

// Naudojimo pavyzdys
const bst = new BinarySearchTree();
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);

const printNode = (value) => console.log(value);

console.log("Vidinis apėjimas:");
bst.inOrderTraverse(printNode);

console.log("Pirminis apėjimas:");
bst.preOrderTraverse(printNode);

console.log("Poordinis apėjimas:");
bst.postOrderTraverse(printNode);

console.log("Mažiausia reikšmė:", bst.min().key);
console.log("Didžiausia reikšmė:", bst.max().key);

console.log("Ieškoti 9:", bst.search(9));
console.log("Ieškoti 2:", bst.search(2));

bst.remove(7);
console.log("Ieškoti 7 po pašalinimo:", bst.search(7));

Išvada

Dvejetainiai paieškos medžiai yra galinga ir universali duomenų struktūra, turinti daugybę pritaikymų. Šiame vadove pateikta išsami BST apžvalga, apimanti jų struktūrą, operacijas ir diegimą naudojant JavaScript. Suprasdami šiame vadove aptartus principus ir metodus, programuotojai visame pasaulyje gali efektyviai naudoti BST sprendžiant įvairias programinės įrangos kūrimo problemas. Nuo pasaulinių duomenų bazių valdymo iki paieškos algoritmų optimizavimo, BST žinios yra neįkainojamas turtas bet kuriam programuotojui.

Tęsiant kelionę kompiuterių mokslo srityje, gilesnis susipažinimas su pažangiomis sąvokomis, tokiomis kaip savaime balansuojantys medžiai ir jų įvairūs diegimai, dar labiau sustiprins jūsų supratimą ir gebėjimus. Toliau praktikuokitės ir eksperimentuokite su skirtingais scenarijais, kad įvaldytumėte efektyvaus dvejetainių paieškos medžių naudojimo meną.